Перейти к основному содержимому

5.01. Работа с объектами

Разработчику Архитектору

Работа с объектами

Пример класса в JS

class Unit {
constructor() {
this.name = "Имя";
this.intel = 10;
this.agility = 10;
this.strength = 10;
this.health = 100;
this.mana = 50;
this.level = 1;
}

get damage() {
return (this.intel + this.agility + this.strength) + (this.level * 2);
}

attack(target) {
console.log(`${this.name} атакует ${target.name} и наносит ${this.damage} единиц урона.`);
target.health -= this.damage;
console.log(`${target.name} теперь имеет ${target.health} здоровья.`);
}
}

const warrior = new Unit();
warrior.name = "Воин";
warrior.intel = 5;
warrior.agility = 15;
warrior.strength = 30;

const mage = new Unit();
mage.name = "Маг";
mage.intel = 35;
mage.agility = 10;
mage.strength = 5;

warrior.attack(mage);
mage.attack(warrior);

Ключевое слово class определяет новый класс. Внутри фигурных скобок размещаются свойства и методы класса.

Метод constructor вызывается автоматически при создании нового объекта с помощью оператора new. В конструкторе инициализируются начальные значения свойств объекта.

Каждое свойство объекта хранит конкретное значение. Свойства определяются через ключевое слово this, которое ссылается на текущий экземпляр класса.

Метод get damage() представляет собой геттер — специальный метод, который вычисляет значение свойства каждый раз при обращении к нему. Геттер позволяет динамически рассчитывать урон на основе текущих характеристик персонажа.

Метод attack принимает целевой объект в качестве параметра. Внутри метода происходит вывод сообщения о нанесённом уроне и изменение здоровья цели.

Оператор new создаёт новый экземпляр класса. После создания объекта можно изменять значения его свойств, присваивая новые значения напрямую.

Для вызова метода объекта используется точечная нотация: имя объекта, точка, имя метода и круглые скобки с аргументами.


Объекты и классы

В JavaScript, с появлением ES6 (2015) используется «синтаксический сахар» над прототипным наследованием – классы. Это позволяет писать код в более привычном ООП-стиле. Благодаря классам, может быть создан объект (некоторые объекты уже создаются – элементы DOM), и можно получить доступ к свойствам или методам класса. К примеру, можно вызвать класс.метод() и сразу вызывать уже существующие стандартные возможности, а также создавать свои.

Объект JavaScript может быть пользовательским (со своими свойствами и методами) или объектом DOM.

Их можно расширять при помощи миксинов (путем объединения свойств и методов из одного или нескольких исходных объектов). Микс - смешивать, смешивают поведение нескольких в один объект.

image-7.png

К примеру, так выглядит базовый объект:

// Базовый объект
const Объект = {
имя: "Основной объект",
метод() {
console.log("Это метод основного объекта.");
}
};

Если обратиться через название объекта и точку, можно получать свойства и вызывать методы - Объект.имя или Объект.метод().

Вышеприведенный пример называется простым созданием объекта (литерал):

let obj = {
key: "value",
anotherKey: "AnotherValue"
};

Это более сложный тип данных, который можно записать в переменную и придать ему свойства в виде «ключ: “значение”». Это может быть, к примеру, пользователь с присущими ему именем, Email и возрастом:

let user = {
name: "Тимур",
email: "timur@mail.com",
age: 30
};

Почему это называется простым созданием? Потому что есть более сложное - с использованим классов или через функцию конструктор.

Можно создать функцию для того, чтобы создавать объекты по шаблону лишь путем вызова метода-конструктора, которому нужно лишь передать аргументы (соответствующие значениям) и функция сама создаст объект:

function Объект(значение) {
this.ключ = значение;
this.другойКлюч = "значение по умолчанию";
}

let obj1 = new Объект("привет");
let obj2 = new Объект("мир");

console.log(obj1); // { ключ: "привет", другойКлюч: "значение по умолчанию" }

Конструктор будет выступать в роли метода, создающего объект, а команда new означает создание нового объекта. Важно не путать с простым литералом, ведь new Объект это именно функция, а не сам объект.

Объекты нужны для создания комплексного набора данных с разными типами, и при работе с JS в крупных системах, как правило, работа с объектами неизбежна. Важное отличие от примитивов в том, что они работают ссылочно, благодаря чему переменная лишь ссылается на объект. Ссылаться на объект user могут несколько переменных, что позволяет не копировать все значение целиком - память будет использоваться как для одного набора данных. Но давайте пока не будем в это погружаться. Это станет понятно позднее.

У объекта могут быть методы - особые функции, которые являются свойством объекта и хранятся в нём. К примеру, мы сделали объект «собака»:

let собака = {
имя: "Тарзан",
лает: function() {
console.log(this.имя + " говорит: Гав-гав!");
}
};

Здесь «лает» является методом объекта «собака». И «this» — это ключевое слово, которое ссылается на сам объект «собака». В дальнейшем мы просто можем вызывать метод через точку:

собака.лает(); // "Тарзан говорит: Гав-гав!"

В ES6 появился и более удобный способ:

let собака = {
имя: "Тарзан",
лает() {
console.log(this.имя + " говорит: Гав-гав!");
},
ест(еда) {
console.log(this.имя + " ест " + еда);
}
};

собака.лает(); // Гав-гав!
собака.ест("корм"); // Тарзан ест корм

Причем метод можно добавить и позднее, не обязательно при создании объекта. К примеру, сначала создать объект, потом добавить метод и вызывать его:

let кот = {
имя: "Барсик"
};

// Добавляем метод позже
кот.мяукает = function() {
console.log(this.имя + " говорит: Мяу!");
};

кот.мяукает(); // "Барсик говорит: Мяу!"

this — это ссылка на объект, который вызывает функцию. Это называется контекст выполнения - this определяется в момент вызова функции, а не при её создании и зависит от способа вызова функции.

Если вызывать this в методе объекта, то this = сам объект. В больших проектах работа с объектами позволит использовать автодополнение в браузере или IDE, когда можно будет указать «кот.» и IDE выведет доступные свойства и методы.

Ранее мы упоминали, что существует деструктивное присваивание — это может быть применено и к объектам. Пример:

let user = { name: "Боб", age: 30 };

let { name, age } = user;

console.log(name); // "Боб"
console.log(age); // 30

prototype — это механизм наследования в JavaScript, основанный на цепочке прототипов (prototype chain). Каждая функция в JS имеет свойство prototype, которое используется, когда функция используется как конструктор (с new).

prototype — это свойство функции-конструктора.

__proto__ — это свойство объекта, указывающее на его прототип (устаревшее, но работает; современный аналог — Object.getPrototypeOf()).

superclass — это родительский класс, от которого наследуется другой класс (дочерний, или подкласс).

Этот термин чаще используется в контексте классов (ES6+) и наследования через extends.


Получение свойств и методов объекта

В JavaScript очень часто приходится работать с объектами, чья структура неочевидна — это может быть объект из API, библиотеки, фреймворка (React, Vue, Express), или даже this в сложной цепочке вызовов. И на практике сталкиваться можно с непониманием того, что есть у объекта. Узнать, какие свойства или методы есть у объекта, можно несколькими способами.

  1. Консоль. Можно открыть консоль в инструментах разработчика (DevTools, F12), поставить точку останова на нужной строке и запустить выполнение кода. После этого, в консоли можно просто написать this и поставить точку - после точки будет отображено всё возможное - так работает автодополнение:

image-8.png

  1. Дерево объекта. Также можно в браузере написать console.log(myObject); - после чего в консоли браузера увидим интерактивное дерево объекта - по нему можно кликать, раскрывать свойства, смотреть методы.

image-9.png

А если написать console.dir(myObject) — это покажет только свойства и методы, без лишней информации. Можете открыть любой сайт и попробовать протестировать, написав console.log(this) или console.dir(this). Если навести мышкой на объект - подсказка покажет тип и структуру.

Если при поиске объекта, к примеру myObject.test() получаем ошибку «Uncaught TypeError: myObject.test is not a function», значит такой функции нет. А если напишем myObject.test, но такого свойства нет, то получим undefined - «неопределенно».

  1. Можно использовать методы для перечисления свойств, что позволит программно узнать, что внутри объекта:
    • Object.keys(obj) вернёт массив собственных перечисляемых свойств ([‘name’, ‘age’]);
    • Object.values(obj) вернёт массив значений свойств ([‘Тимур’, ‘30’]);
    • Object.entries(obj) вернет свойства целиком ([‘name’, ‘Тимур’], [‘age’, ‘30’]);
    • for…in позволит перебирать свойства, к примеру:
for (let key in user) {
console.log(key, user[key]);
}
  • Object.getOwnPropertyNames(obj) покажет все собственные свойства, включая неперечисляемые;
  • Reflect.ownKeys(obj) покажет все собственные ключи, включая символы (Symbol) и неперечисляемые.
  1. VS Code и другие IDE. Если объект имеет типизацию (например, JSDoc, TypeScript или встроен в JS (базовый объект), то VS Code покажет подсказки при наведении и автодополнении. К примеру:
const arr = [1, 2, 3];
arr. // → сразу выпадает список методов: push, pop, map, filter и т.д.

Если же объект из библиотеки или фреймворка, то разумеется, сначала следует прочитать документацию, которая всегда описывает структуру объектов. И опять же, можно смотреть в консоли, как описано выше.


Миксин

Мы можем создать миксины:

// Миксин 1
const Миксин1 = {
миксин1Метод() {
console.log("Это метод из Миксина 1.");
}
};
// Миксин 2
const Миксин2 = {
миксин2Метод() {
console.log("Это метод из Миксина 2.");
}
};

Теперь это два «дополнительных объекта», которые имеют свои методы миксин1Метод() и миксин2Метод().

После этого мы можем либо использовать Object.assign, чтобы скопировать свойства и методы из Миксин1 и Миксин2 в Объект:

// Применяем миксины напрямую через Object.assign
Object.assign(Объект, Миксин1, Миксин2);

// Используем расширенный объект
Объект.метод(); // Это метод основного объекта.
Объект.миксин1Метод(); // Это метод из Миксина 1.
Объект.миксин2Метод(); // Это метод из Миксина 2.

…либо использовать функцию смешивания:

// Функция для смешивания (применения миксинов)
function применитьМиксины(целевойОбъект, ...миксины) {
миксины.forEach(миксин => {
Object.assign(целевойОбъект, миксин);
});
}
// Применяем миксины к объекту
применитьМиксины(Объект, Миксин1, Миксин2);

Функция смешивания актуальна как посредник для случаев, когда объектов может быть несколько и нужно часто применять их к разным объектам, или когда работа ведётся с большим количеством миксинов. Она используется для упрощения процесса объединения свойств и методов из нескольких миксинов в один объект.

// Применяем миксины к объектам
применитьМиксины(Объект1, Миксин1, Миксин2);
применитьМиксины(Объект2, Миксин1);
// Используем расширенные объекты
Объект1.метод(); // Это метод первого объекта.
Объект1.миксин1Метод(); // Это метод из Миксина 1.
Объект1.миксин2Метод(); // Это метод из Миксина 2.
Объект2.метод(); // Это метод второго объекта.
Объект2.миксин1Метод(); // Это метод из Миксина 1.
// Объект2.миксин2Метод(); // Ошибка: метод миксин2Метод не определён, так как Миксин2 не был применён.

Базовые операции с объектами и классами

Объявление класса:

class Person {
// Конструктор (вызывается при создании объекта)
constructor(name, age) {
this.name = name; // Свойство
this.age = age;
}

// Метод
greet() {
return `Привет, я ${this.name}!`;
}
}

Создание объекта (экземпляра класса):

const person = new Person('Том', 25);
console.log(person.greet()); // "Привет, я Том!"

Свойства – это переменные, принадлежащие объекту/классу.

Публичные свойства доступны извне класса:

class Car {
constructor(brand) {
this.brand = brand; // Публичное свойство
}
}

const myCar = new Car('Toyota');
console.log(myCar.brand); // "Toyota"

Приватные свойства (с префиксом #) доступны только внутри класса:

class User {
#password; // Приватное свойство

constructor(login, password) {
this.login = login;
this.#password = password;
}
}

const user = new User('admin', '12345');
console.log(user.#password); // Ошибка!

Статические свойства (static) принадлежат классу, а не экземплярам:

class MathUtils {
static PI = 3.14; // Статическое свойство
}

console.log(MathUtils.PI); // 3.14

Методы – это функции, принадлежащие классу/объекту.

Публичные методы, как и свойства, доступны извне. Приватные методы, как и свойства, доступны внутри класса и имеют префикс #.

Пример – класс для работы с API:

class ApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}

async get(endpoint) {
const response = await fetch(`${this.baseUrl}/${endpoint}`);
return response.json();
}

async post(endpoint, data) {
const response = await fetch(`${this.baseUrl}/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data)
});
return response.json();
}
}

// Использование
const api = new ApiClient('https://api.example.com');
api.get('users').then(users => console.log(users));

Создание объектов

Литералы объектов

Литерал объекта — это выражение, создающее объект с указанными свойствами и методами:

const user = {
name: "Алексей",
age: 28,
greet() {
return `Привет, меня зовут ${this.name}`;
}
};

Конструкторы через функции

Функция-конструктор создаёт объекты с заданной структурой:

function User(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
return `Привет, меня зовут ${this.name}`;
};
}

const user1 = new User("Мария", 25);
const user2 = new User("Иван", 30);

Object.create()

Метод Object.create() создаёт новый объект с указанным прототипом:

const parent = {
greet() {
return `Привет от ${this.name}`;
}
};

const child = Object.create(parent);
child.name = "Дочерний объект";
console.log(child.greet()); // "Привет от Дочерний объект"

Свойства объектов

Геттеры и сеттеры

Геттеры и сеттеры позволяют контролировать доступ к свойствам:

const user = {
_name: "Анна",
_age: 25,

get name() {
return this._name.toUpperCase();
},

set name(value) {
if (value.length > 2) {
this._name = value;
}
},

get age() {
return this._age;
},

set age(value) {
if (value >= 0 && value <= 120) {
this._age = value;
}
}
};

console.log(user.name); // "АННА"
user.name = "Петр";
console.log(user.name); // "ПЕТР"

Дескрипторы свойств

Дескрипторы описывают поведение свойств объекта:

const obj = {};

Object.defineProperty(obj, 'name', {
value: 'Тест',
writable: false, // нельзя изменить
enumerable: true, // видно в циклах
configurable: false // нельзя удалить или изменить дескриптор
});

obj.name = 'Новое'; // Не сработает
console.log(obj.name); // "Тест"

Полный пример с геттером и сеттером через дескриптор:

const person = {};
let _age = 0;

Object.defineProperty(person, 'age', {
get() {
return _age;
},
set(value) {
if (value >= 0 && value <= 120) {
_age = value;
}
},
enumerable: true,
configurable: true
});

person.age = 25;
console.log(person.age); // 25
person.age = 200; // Не сработает
console.log(person.age); // 25

Прототипное наследование

Цепочка прототипов

Каждый объект имеет внутреннее свойство [[Prototype]], ссылающееся на другой объект — прототип. При обращении к свойству объекта, если его нет в самом объекте, поиск продолжается в прототипе:

const animal = {
eats: true,
walk() {
console.log("Животное ходит");
}
};

const rabbit = Object.create(animal);
rabbit.jumps = true;
rabbit.walk(); // "Животное ходит" — метод из прототипа

console.log(rabbit.eats); // true — свойство из прототипа
console.log(rabbit.jumps); // true — собственное свойство

Изменение прототипа

Современный способ изменения прототипа — Object.setPrototypeOf():

const parent = { parentProp: "родитель" };
const child = { childProp: "ребёнок" };

Object.setPrototypeOf(child, parent);
console.log(child.parentProp); // "родитель"

Проверка прототипа

Метод isPrototypeOf() проверяет, является ли объект прототипом другого:

const parent = {};
const child = Object.create(parent);

console.log(parent.isPrototypeOf(child)); // true
console.log(Object.prototype.isPrototypeOf(child)); // true

Методы объекта Object

Object.keys(), values(), entries()

Эти методы возвращают массивы ключей, значений и пар ключ-значение:

const user = {
name: "Олег",
age: 30,
city: "Москва"
};

console.log(Object.keys(user)); // ["name", "age", "city"]
console.log(Object.values(user)); // ["Олег", 30, "Москва"]
console.log(Object.entries(user)); // [["name","Олег"],["age",30],["city","Москва"]]

Object.assign()

Копирует свойства из одного или нескольких источников в целевой объект:

const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
console.log(target); // { a: 1, b: 2, c: 3 }

Глубокое копирование требует рекурсивного подхода:

function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}

if (obj instanceof Date) {
return new Date(obj);
}

if (obj instanceof Array) {
return obj.map(item => deepClone(item));
}

if (obj instanceof Object) {
const clonedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}

return obj;
}

Object.defineProperty() и Object.defineProperties()

Определяют или изменяют свойства объекта с точным контролем:

const obj = {};

Object.defineProperties(obj, {
name: {
value: "Тест",
writable: true,
enumerable: true,
configurable: true
},
id: {
value: 1,
writable: false,
enumerable: false,
configurable: false
}
});

Object.freeze(), seal(), preventExtensions()

Эти методы ограничивают изменение объекта:

const obj = { name: "Тест" };

// Запрещает добавление новых свойств
Object.preventExtensions(obj);
obj.newProp = "value"; // Не сработает в строгом режиме

// Запрещает добавление и удаление свойств
const sealed = { name: "Тест" };
Object.seal(sealed);
sealed.newProp = "value"; // Не сработает
delete sealed.name; // Не сработает

// Полностью блокирует изменения
const frozen = { name: "Тест" };
Object.freeze(frozen);
frozen.name = "Новое"; // Не сработает
frozen.newProp = "value"; // Не сработает
delete frozen.name; // Не сработает

Object.hasOwnProperty()

Проверяет, является ли свойство собственным (не унаследованным):

const parent = { parentProp: "родитель" };
const child = Object.create(parent);
child.childProp = "ребёнок";

console.log(child.hasOwnProperty("childProp")); // true
console.log(child.hasOwnProperty("parentProp")); // false
console.log("parentProp" in child); // true — проверяет включая прототипы

Function как конструктор

Функция в JavaScript может выступать в роли конструктора при вызове с оператором new:

function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
return `Привет, меня зовут ${this.name}`;
};
}

const person = new Person("Сергей", 35);
console.log(person.name); // "Сергей"
console.log(person.greet()); // "Привет, меня зовут Сергей"

Свойство prototype функции-конструктора становится прототипом созданных объектов:

function Car(brand) {
this.brand = brand;
}

Car.prototype.start = function() {
console.log(`${this.brand} заводится`);
};

const myCar = new Car("Toyota");
myCar.start(); // "Toyota заводится"

Синтаксис классов

Базовый класс

Класс — это шаблон для создания объектов:

class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}

getArea() {
return this.width * this.height;
}

getPerimeter() {
return 2 * (this.width + this.height);
}
}

const rect = new Rectangle(10, 5);
console.log(rect.getArea()); // 50
console.log(rect.getPerimeter()); // 30

Конструктор

Конструктор инициализирует новый объект:

class User {
constructor(name, email) {
this.name = name;
this.email = email;
this.createdAt = new Date();
}

getInfo() {
return `${this.name} (${this.email})`;
}
}

const user = new User("Анна", "anna@example.com");
console.log(user.getInfo()); // "Анна (anna@example.com)"

Наследование через extends

Ключевое слово extends создаёт дочерний класс:

class Animal {
constructor(name) {
this.name = name;
}

speak() {
console.log(`${this.name} издаёт звук`);
}
}

class Dog extends Animal {
constructor(name, breed) {
super(name); // Вызов конструктора родителя
this.breed = breed;
}

bark() {
console.log(`${this.name} лает`);
}
}

const dog = new Dog("Рекс", "Овчарка");
dog.speak(); // "Рекс издаёт звук"
dog.bark(); // "Рекс лает"

Методы super

Ключевое слово super обращается к методам родительского класса:

class Parent {
greet() {
return "Привет от родителя";
}
}

class Child extends Parent {
greet() {
return super.greet() + " и от ребёнка";
}
}

const child = new Child();
console.log(child.greet()); // "Привет от родителя и от ребёнка"

В конструкторе дочернего класса super() должен вызываться до использования this:

class Parent {
constructor(name) {
this.name = name;
}
}

class Child extends Parent {
constructor(name, age) {
super(name); // Обязательно первым
this.age = age;
}
}

Статические методы и поля

Статические члены принадлежат классу, а не экземплярам:

class MathUtils {
static PI = 3.14159;

static add(a, b) {
return a + b;
}

static multiply(a, b) {
return a * b;
}
}

console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.multiply(4, 2)); // 8

// Нельзя вызывать статические методы через экземпляр
const utils = new MathUtils();
// utils.add(1, 2); // Ошибка

Приватные поля и методы

Приватные члены доступны только внутри класса:

class BankAccount {
#balance = 0;
#pinCode;

constructor(owner, pinCode) {
this.owner = owner;
this.#pinCode = pinCode;
}

#validatePin(pin) {
return pin === this.#pinCode;
}

deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return true;
}
return false;
}

withdraw(amount, pin) {
if (this.#validatePin(pin) && amount <= this.#balance) {
this.#balance -= amount;
return true;
}
return false;
}

getBalance(pin) {
if (this.#validatePin(pin)) {
return this.#balance;
}
return "Неверный пин-код";
}
}

const account = new BankAccount("Иван", "1234");
account.deposit(1000);
console.log(account.getBalance("1234")); // 1000
console.log(account.getBalance("0000")); // "Неверный пин-код"
// console.log(account.#balance); // Ошибка

Статические блоки инициализации

Статические блоки выполняют сложную инициализацию статических полей:

class Config {
static DEFAULT_TIMEOUT;
static MAX_RETRIES;
static API_URL;

static {
try {
const env = process.env.NODE_ENV || 'development';

if (env === 'production') {
this.DEFAULT_TIMEOUT = 30000;
this.MAX_RETRIES = 3;
this.API_URL = 'https://api.example.com';
} else {
this.DEFAULT_TIMEOUT = 5000;
this.MAX_RETRIES = 1;
this.API_URL = 'https://dev-api.example.com';
}

console.log('Конфигурация загружена');
} catch (error) {
console.error('Ошибка инициализации конфигурации:', error);
this.DEFAULT_TIMEOUT = 10000;
this.MAX_RETRIES = 2;
this.API_URL = 'https://fallback-api.example.com';
}
}
}

console.log(Config.API_URL);

Практические примеры

Построение иерархии классов

class Shape {
constructor(color = 'black') {
this.color = color;
}

draw() {
console.log(`Рисую фигуру цветом ${this.color}`);
}
}

class Circle extends Shape {
constructor(radius, color) {
super(color);
this.radius = radius;
}

getArea() {
return Math.PI * this.radius * this.radius;
}

draw() {
console.log(`Рисую круг радиусом ${this.radius}`);
super.draw();
}
}

class Rectangle extends Shape {
constructor(width, height, color) {
super(color);
this.width = width;
this.height = height;
}

getArea() {
return this.width * this.height;
}

draw() {
console.log(`Рисую прямоугольник ${this.width}x${this.height}`);
super.draw();
}
}

const circle = new Circle(5, 'red');
const rect = new Rectangle(10, 20, 'blue');

circle.draw();
console.log(`Площадь круга: ${circle.getArea()}`);

rect.draw();
console.log(`Площадь прямоугольника: ${rect.getArea()}`);

Фабрика объектов

class UserFactory {
static #userCount = 0;

static createUser(type, name, email) {
this.#userCount++;

switch(type) {
case 'admin':
return new AdminUser(name, email, this.#userCount);
case 'guest':
return new GuestUser(name, email, this.#userCount);
default:
return new RegularUser(name, email, this.#userCount);
}
}

static getUserCount() {
return this.#userCount;
}
}

class BaseUser {
constructor(name, email, id) {
this.name = name;
this.email = email;
this.id = id;
this.createdAt = new Date();
}

getInfo() {
return `${this.name} (${this.email})`;
}
}

class AdminUser extends BaseUser {
#permissions = ['read', 'write', 'delete', 'admin'];

hasPermission(permission) {
return this.#permissions.includes(permission);
}

getRole() {
return 'Administrator';
}
}

class GuestUser extends BaseUser {
#permissions = ['read'];

hasPermission(permission) {
return this.#permissions.includes(permission);
}

getRole() {
return 'Guest';
}
}

class RegularUser extends BaseUser {
#permissions = ['read', 'write'];

hasPermission(permission) {
return this.#permissions.includes(permission);
}

getRole() {
return 'User';
}
}

const admin = UserFactory.createUser('admin', 'Админ', 'admin@example.com');
const guest = UserFactory.createUser('guest', 'Гость', 'guest@example.com');
const user = UserFactory.createUser('user', 'Пользователь', 'user@example.com');

console.log(`Создано пользователей: ${UserFactory.getUserCount()}`);
console.log(`${admin.getInfo()} - ${admin.getRole()}`);
console.log(`${guest.getInfo()} - ${guest.getRole()}`);
console.log(`${user.getInfo()} - ${user.getRole()}`);

Базовые объекты

Базовые встроенные объекты

Базовые встроенные объекты

JavaScript предоставляет стандартные объекты-конструкторы для работы с разными типами данных:

ОбъектОписаниеПример использования
ArrayРабота с массивами[1, 2, 3].map(x => x * 2)
BooleanЛогические значенияnew Boolean(true)
MathМатематические операцииMath.PI, Math.random()
NumberЧисла и методыNumber.parseInt("42")
StringСтроки и методы"Hello".toUpperCase()
GlobalГлобальные функцииisNaN(), eval()

Глобальные свойства

globalThis

Глобальный объект globalThis обеспечивает единый способ доступа к глобальному объекту в разных средах выполнения JavaScript. В браузере глобальным объектом является window, в Node.js — global, а globalThis работает везде одинаково:

// В браузере
console.log(globalThis === window); // true

// В Node.js
console.log(globalThis === global); // true

// Универсальный доступ
globalThis.myGlobalVar = "Значение";
console.log(globalThis.myGlobalVar); // "Значение"

Infinity

Свойство Infinity представляет бесконечность — числовое значение, большее любого другого числа:

console.log(Infinity); // Infinity
console.log(10 / 0); // Infinity
console.log(Infinity + 1); // Infinity
console.log(Infinity * 2); // Infinity
console.log(Infinity - Infinity); // NaN

// Проверка на бесконечность
console.log(isFinite(Infinity)); // false
console.log(isFinite(100)); // true
console.log(Number.isFinite(Infinity)); // false

Отрицательная бесконечность:

console.log(-Infinity); // -Infinity
console.log(-10 / 0); // -Infinity

NaN

NaN (Not-a-Number) представляет результат неопределённой или некорректной математической операции:

console.log(NaN); // NaN
console.log(0 / 0); // NaN
console.log(Math.sqrt(-1)); // NaN
console.log(parseInt("текст")); // NaN
console.log("строка" * 5); // NaN

Особенность NaN — он не равен ничему, включая самого себя:

console.log(NaN === NaN); // false
console.log(NaN == NaN); // false

Проверка на NaN:

console.log(isNaN(NaN)); // true
console.log(isNaN("текст")); // true — преобразует в число сначала
console.log(isNaN(123)); // false

// Более точная проверка
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("текст")); // false — не преобразует
console.log(Number.isNaN(123)); // false

undefined

undefined означает, что переменная объявлена, но не инициализирована, или свойство объекта отсутствует:

let x;
console.log(x); // undefined
console.log(typeof x); // "undefined"

const obj = {};
console.log(obj.nonExistentProperty); // undefined

function noReturn() {}
console.log(noReturn()); // undefined

Проверка на undefined:

let value;

if (value === undefined) {
console.log("Значение не определено");
}

// Безопасная проверка
if (typeof value === "undefined") {
console.log("Переменная не определена");
}

Глобальные функции

eval()

Функция eval() выполняет строку как код JavaScript:

const code = "console.log('Привет, мир!')";
eval(code); // "Привет, мир!"

const result = eval("2 + 2 * 3");
console.log(result); // 8

const x = 10;
const y = 20;
console.log(eval("x + y")); // 30

Опасности использования eval():

  • Выполнение непроверенного кода создаёт уязвимости
  • Замедляет выполнение программы
  • Нарушает безопасность приложения
  • Затрудняет отладку кода

Безопасные альтернативы:

// Вместо eval для вычисления математических выражений
const expression = "2 + 2 * 3";
const result = Function('"use strict";return (' + expression + ')')();
console.log(result); // 8

// Для парсинга JSON
const jsonString = '{"name":"Иван","age":30}';
const obj = JSON.parse(jsonString);
console.log(obj.name); // "Иван"

isFinite()

Функция isFinite() проверяет, является ли значение конечным числом:

console.log(isFinite(123)); // true
console.log(isFinite(-123)); // true
console.log(isFinite(0)); // true
console.log(isFinite(Infinity)); // false
console.log(isFinite(-Infinity)); // false
console.log(isFinite(NaN)); // false

// Строки преобразуются в числа
console.log(isFinite("123")); // true
console.log(isFinite("12.5")); // true
console.log(isFinite("текст")); // false
console.log(isFinite("")); // true (пустая строка → 0)

Более строгая проверка через Number.isFinite():

console.log(Number.isFinite(123)); // true
console.log(Number.isFinite("123")); // false — не преобразует строку
console.log(Number.isFinite(NaN)); // false

isNaN()

Функция isNaN() проверяет, является ли значение NaN:

console.log(isNaN(NaN)); // true
console.log(isNaN("текст")); // true — "текст" → NaN
console.log(isNaN(123)); // false
console.log(isNaN("123")); // false — "123" → 123
console.log(isNaN("")); // false — пустая строка → 0
console.log(isNaN(" ")); // false — пробел → 0

Разница между isNaN() и Number.isNaN():

console.log(isNaN("привет")); // true
console.log(Number.isNaN("привет")); // false — не преобразует

console.log(isNaN(NaN)); // true
console.log(Number.isNaN(NaN)); // true

console.log(isNaN(undefined)); // true
console.log(Number.isNaN(undefined)); // false

parseInt()

Функция parseInt() преобразует строку в целое число:

console.log(parseInt("42")); // 42
console.log(parseInt("42px")); // 42
console.log(parseInt("10.5")); // 10 — дробная часть отбрасывается
console.log(parseInt("0xFF")); // 255 — шестнадцатеричное
console.log(parseInt("10", 2)); // 2 — двоичная система
console.log(parseInt("10", 8)); // 8 — восьмеричная система
console.log(parseInt("10", 16)); // 16 — шестнадцатеричная система

// Обработка некорректных значений
console.log(parseInt("текст")); // NaN
console.log(parseInt("")); // NaN
console.log(parseInt(" 42 ")); // 42 — пробелы игнорируются

Явное указание системы счисления:

console.log(parseInt("10", 10)); // 10
console.log(parseInt("FF", 16)); // 255
console.log(parseInt("77", 8)); // 63
console.log(parseInt("1010", 2)); // 10

parseFloat()

Функция parseFloat() преобразует строку в число с плавающей точкой:

console.log(parseFloat("3.14")); // 3.14
console.log(parseFloat("3.14px")); // 3.14
console.log(parseFloat("10")); // 10
console.log(parseFloat("10.00")); // 10
console.log(parseFloat("2.5e3")); // 2500 — экспоненциальная запись
console.log(parseFloat("Infinity")); // Infinity

// Обработка некорректных значений
console.log(parseFloat("текст")); // NaN
console.log(parseFloat("")); // NaN
console.log(parseFloat(" 3.14 ")); // 3.14

Разница между parseInt() и parseFloat():

console.log(parseInt("10.99")); // 10 — только целая часть
console.log(parseFloat("10.99")); // 10.99 — сохраняет дробную часть

console.log(parseInt("3.5e2")); // 3
console.log(parseFloat("3.5e2")); // 350

Кодирование и декодирование URI

Функции для работы с URL и URI:

encodeURI()

Кодирует полный URI, сохраняя разделители:

const uri = "https://сайт.рф/путь?параметр=значение с пробелами";
console.log(encodeURI(uri));
// "https://сайт.рф/путь?параметр=значение%20с%20пробелами"

// Сохраняет разделители URI
console.log(encodeURI("https://example.com/путь/страница"));
// "https://example.com/%D0%BF%D1%83%D1%82%D1%8C/%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0"

encodeURIComponent()

Кодирует компонент URI, включая разделители:

const component = "значение с пробелами и / символами";
console.log(encodeURIComponent(component));
// "значение%20с%20пробелами%20и%20%2F%20символами"

// Используется для кодирования параметров запроса
const params = {
name: "Иван Петров",
city: "Москва/Санкт-Петербург"
};

const queryString = Object.entries(params)
.map(([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`
)
.join("&");

console.log(queryString);
// "name=%D0%98%D0%B2%D0%B0%D0%BD%20%D0%9F%D0%B5%D1%82%D1%80%D0%BE%D0%B2&city=%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%2F%D0%A1%D0%B0%D0%BD%D0%BA%D1%82-%D0%9F%D0%B5%D1%82%D0%B5%D1%80%D0%B1%D1%83%D1%80%D0%B3"

decodeURI()

Декодирует полный URI:

const encoded = "https://сайт.рф/путь?параметр=значение%20с%20пробелами";
console.log(decodeURI(encoded));
// "https://сайт.рф/путь?параметр=значение с пробелами"

decodeURIComponent()

Декодирует компонент URI:

const encodedComponent = "значение%20с%20пробелами%20и%20%2F%20символами";
console.log(decodeURIComponent(encodedComponent));
// "значение с пробелами и / символами"

// Пример декодирования параметров
const url = "https://example.com/?name=%D0%98%D0%B2%D0%B0%D0%BD&city=%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0";
const paramsString = url.split("?")[1];
const paramsArray = paramsString.split("&");

const params = {};
paramsArray.forEach(param => {
const [key, value] = param.split("=");
params[decodeURIComponent(key)] = decodeURIComponent(value);
});

console.log(params);
// { name: "Иван", city: "Москва" }

Разница между encodeURI() и encodeURIComponent():

const url = "https://example.com/путь?параметр=значение с пробелами";

console.log(encodeURI(url));
// "https://example.com/%D0%BF%D1%83%D1%82%D1%8C?параметр=значение%20с%20пробелами"
// Символы ? и = сохраняются

console.log(encodeURIComponent(url));
// "https%3A%2F%2Fexample.com%2F%D0%BF%D1%83%D1%82%D1%8C%3F%D0%BF%D0%B0%D1%80%D0%B0%D0%BC%D0%B5%D1%82%D1%80%3D%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BF%D1%80%D0%BE%D0%B1%D0%B5%D0%BB%D0%B0%D0%BC%D0%B8"
// Все специальные символы кодируются

Устаревшие функции

escape() и unescape()

Эти функции считаются устаревшими и не должны использоваться в современном коде:

// Устаревший способ
const oldEncoded = escape("Привет мир!");
console.log(oldEncoded); // "%u041F%u0440%u0438%u0432%u0435%u0442%20%u043C%u0438%u0440%21"

const oldDecoded = unescape(oldEncoded);
console.log(oldDecoded); // "Привет мир!"

// Современная замена
const newEncoded = encodeURIComponent("Привет мир!");
console.log(newEncoded); // "%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%BC%D0%B8%D1%80%21"

const newDecoded = decodeURIComponent(newEncoded);
console.log(newDecoded); // "Привет мир!"

Фундаментальные объекты

Boolean

Объект Boolean представляет логическое значение:

// Создание объектов Boolean
const bool1 = new Boolean(true);
const bool2 = new Boolean(false);
const bool3 = new Boolean("текст"); // любое непустое значение → true

console.log(bool1); // Boolean { true }
console.log(bool2); // Boolean { false }
console.log(bool3); // Boolean { true }

// Приведение к примитиву
console.log(bool1.valueOf()); // true
console.log(bool2.valueOf()); // false

// Автоматическое приведение в условиях
if (bool1) {
console.log("Истина"); // Выполнится
}

if (bool2) {
console.log("Истина"); // Не выполнится
}

Разница между примитивом и объектом:

const primitive = true;
const object = new Boolean(true);

console.log(typeof primitive); // "boolean"
console.log(typeof object); // "object"

console.log(primitive === true); // true
console.log(object === true); // false — объект не равен примитиву
console.log(object == true); // true — приведение типов

// Объект Boolean всегда истинен в логическом контексте
if (new Boolean(false)) {
console.log("Это выполнится"); // Выполнится
}

Symbol

Символы — это уникальные идентификаторы:

// Создание символов
const sym1 = Symbol();
const sym2 = Symbol("описание");
const sym3 = Symbol("описание");

console.log(sym1); // Symbol()
console.log(sym2); // Symbol(описание)
console.log(sym3); // Symbol(описание)

// Символы всегда уникальны
console.log(sym2 === sym3); // false
console.log(Symbol() === Symbol()); // false

Глобальный реестр символов:

const sym1 = Symbol.for("ключ");
const sym2 = Symbol.for("ключ");

console.log(sym1 === sym2); // true — один и тот же символ из реестра

console.log(Symbol.keyFor(sym1)); // "ключ"

Использование символов как ключей свойств:

const id = Symbol("id");
const name = Symbol("name");

const user = {
[id]: 12345,
[name]: "Алексей",
age: 30
};

console.log(user[id]); // 12345
console.log(user[name]); // "Алексей"
console.log(user.age); // 30

// Символьные свойства не видны в обычных циклах
for (let key in user) {
console.log(key); // только "age"
}

console.log(Object.keys(user)); // ["age"]
console.log(Object.getOwnPropertyNames(user)); // ["age"]

// Получение символьных ключей
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id), Symbol(name)]

// Получение всех ключей
const allKeys = [...Object.getOwnPropertyNames(user), ...Object.getOwnPropertySymbols(user)];
console.log(allKeys); // ["age", Symbol(id), Symbol(name)]

Символы как константы:

const EventType = {
CLICK: Symbol("click"),
HOVER: Symbol("hover"),
DRAG: Symbol("drag")
};

function handleEvent(eventType) {
switch (eventType) {
case EventType.CLICK:
console.log("Обработка клика");
break;
case EventType.HOVER:
console.log("Обработка наведения");
break;
case EventType.DRAG:
console.log("Обработка перетаскивания");
break;
}
}

handleEvent(EventType.CLICK); // "Обработка клика"

Error и его подтипы

Объект Error представляет ошибку во время выполнения:

// Создание ошибки
const error = new Error("Произошла ошибка");
console.log(error.message); // "Произошла ошибка"
console.log(error.name); // "Error"
console.log(error.stack); // Стек вызовов

// Генерация ошибки
throw new Error("Что-то пошло не так");

// Обработка ошибки
try {
throw new Error("Ошибка в блоке try");
} catch (error) {
console.log(error.message); // "Ошибка в блоке try"
console.log(error.name); // "Error"
}

EvalError

Ошибка при работе с функцией eval():

try {
throw new EvalError("Ошибка при вычислении");
} catch (error) {
console.log(error.name); // "EvalError"
console.log(error.message); // "Ошибка при вычислении"
}

RangeError

Ошибка при выходе за пределы допустимого диапазона:

// Примеры возникновения RangeError
try {
const arr = new Array(-1); // Отрицательная длина
} catch (error) {
console.log(error.name); // "RangeError"
}

try {
(123.456).toFixed(1000); // Слишком много знаков
} catch (error) {
console.log(error.name); // "RangeError"
}

// Создание своей ошибки диапазона
function setAge(age) {
if (age < 0 || age > 150) {
throw new RangeError("Возраст должен быть от 0 до 150");
}
return age;
}

try {
setAge(200);
} catch (error) {
console.log(error.message); // "Возраст должен быть от 0 до 150"
}

ReferenceError

Ошибка при обращении к несуществующей переменной:

try {
console.log(nonExistentVariable);
} catch (error) {
console.log(error.name); // "ReferenceError"
}

// Создание своей ошибки ссылки
function accessProperty(obj, prop) {
if (!(prop in obj)) {
throw new ReferenceError(`Свойство "${prop}" не существует`);
}
return obj[prop];
}

const user = { name: "Иван" };
try {
accessProperty(user, "age");
} catch (error) {
console.log(error.message); // "Свойство "age" не существует"
}

SyntaxError

Ошибка синтаксиса в коде:

try {
eval("function test( { return 1; }"); // Неправильный синтаксис
} catch (error) {
console.log(error.name); // "SyntaxError"
}

// Создание своей ошибки синтаксиса
function validateJSON(jsonString) {
try {
JSON.parse(jsonString);
} catch (error) {
if (error instanceof SyntaxError) {
throw new SyntaxError("Некорректный JSON: " + error.message);
}
throw error;
}
}

try {
validateJSON("{ name: 'Иван' }"); // Неправильные кавычки
} catch (error) {
console.log(error.message); // "Некорректный JSON: ..."
}

TypeError

Ошибка при применении операции к неподходящему типу:

try {
null.someMethod(); // null не имеет методов
} catch (error) {
console.log(error.name); // "TypeError"
}

try {
const num = 42;
num(); // Число не является функцией
} catch (error) {
console.log(error.name); // "TypeError"
}

// Создание своей ошибки типа
function divide(a, b) {
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("Оба аргумента должны быть числами");
}
if (b === 0) {
throw new TypeError("Деление на ноль запрещено");
}
return a / b;
}

try {
divide("10", 2);
} catch (error) {
console.log(error.message); // "Оба аргумента должны быть числами"
}

URIError

Ошибка при работе с функциями кодирования URI:

try {
decodeURIComponent("%"); // Некорректная последовательность
} catch (error) {
console.log(error.name); // "URIError"
}

// Создание своей ошибки URI
function safeDecode(uriComponent) {
try {
return decodeURIComponent(uriComponent);
} catch (error) {
if (error instanceof URIError) {
throw new URIError(`Некорректный URI-компонент: ${uriComponent}`);
}
throw error;
}
}

try {
safeDecode("%E0%A4%A");
} catch (error) {
console.log(error.message); // "Некорректный URI-компонент: %E0%A4%A"
}

AggregateError

Представляет несколько ошибок одновременно:

// Используется с Promise.any()
const promises = [
Promise.reject(new Error("Ошибка 1")),
Promise.reject(new Error("Ошибка 2")),
Promise.reject(new Error("Ошибка 3"))
];

Promise.any(promises)
.then(result => console.log(result))
.catch(error => {
console.log(error.name); // "AggregateError"
console.log(error.errors.length); // 3
error.errors.forEach((err, index) => {
console.log(`Ошибка ${index + 1}: ${err.message}`);
});
});

// Создание своего AggregateError
const errors = [
new TypeError("Тип 1"),
new RangeError("Диапазон 1"),
new Error("Обычная ошибка")
];

const aggregate = new AggregateError(errors, "Несколько ошибок произошло");
console.log(aggregate.message); // "Несколько ошибок произошло"
console.log(aggregate.errors.length); // 3

SuppressedError

Представляет ошибку, подавленную другой ошибкой (новый функционал):

// Используется при обработке ошибок с подавлением
try {
try {
throw new Error("Первичная ошибка");
} catch (primaryError) {
try {
// Код очистки, который тоже может выбросить ошибку
throw new Error("Ошибка очистки");
} catch (cleanupError) {
// Подавляем ошибку очистки
throw new SuppressedError(cleanupError, primaryError, "Ошибка при очистке");
}
}
} catch (error) {
console.log(error.name); // "SuppressedError"
console.log(error.error); // Ошибка очистки
console.log(error.suppressed); // Первичная ошибка
}

InternalError (не стандартный)

Нестандартный объект ошибки, используемый в некоторых средах:

// Этот объект не является частью стандарта ECMAScript
// Может встречаться в старых версиях некоторых браузеров
try {
// Очень глубокая рекурсия
function recurse() {
recurse();
}
recurse();
} catch (error) {
console.log(error.name); // Может быть "InternalError" или "RangeError"
}

Практические примеры

Валидация данных с использованием глобальных функций

class DataValidator {
static isValidNumber(value) {
return !isNaN(parseFloat(value)) && isFinite(value);
}

static parseInteger(value, defaultValue = 0) {
const parsed = parseInt(value, 10);
return this.isValidNumber(parsed) ? parsed : defaultValue;
}

static parseFloat(value, defaultValue = 0.0) {
const parsed = parseFloat(value);
return this.isValidNumber(parsed) ? parsed : defaultValue;
}

static isValidURL(url) {
try {
new URL(url);
return true;
} catch {
return false;
}
}

static encodeURLParams(params) {
return Object.entries(params)
.map(([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`
)
.join("&");
}

static decodeURLParams(queryString) {
const params = {};
queryString.split("&").forEach(param => {
const [key, value] = param.split("=");
params[decodeURIComponent(key)] = decodeURIComponent(value || "");
});
return params;
}
}

// Использование
console.log(DataValidator.isValidNumber("123")); // true
console.log(DataValidator.isValidNumber("текст")); // false
console.log(DataValidator.parseInteger("42")); // 42
console.log(DataValidator.parseInteger("текст")); // 0

const urlParams = { name: "Иван Петров", city: "Москва" };
const encoded = DataValidator.encodeURLParams(urlParams);
console.log(encoded); // "name=%D0%98%D0%B2%D0%B0%D0%BD%20%D0%9F%D0%B5%D1%82%D1%80%D0%BE%D0%B2&city=%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0"

const decoded = DataValidator.decodeURLParams(encoded);
console.log(decoded); // { name: "Иван Петров", city: "Москва" }

Создание кастомных ошибок

class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
this.timestamp = new Date().toISOString();
}
}

class DatabaseError extends Error {
constructor(message, query, code) {
super(message);
this.name = "DatabaseError";
this.query = query;
this.code = code;
this.timestamp = new Date().toISOString();
}
}

class AuthenticationError extends Error {
constructor(message, userId = null) {
super(message);
this.name = "AuthenticationError";
this.userId = userId;
this.timestamp = new Date().toISOString();
}
}

// Использование
function validateUser(user) {
if (!user.name || user.name.length < 2) {
throw new ValidationError("Имя должно содержать минимум 2 символа", "name");
}

if (!user.email || !user.email.includes("@")) {
throw new ValidationError("Некорректный email", "email");
}

if (user.age < 0 || user.age > 150) {
throw new ValidationError("Некорректный возраст", "age");
}

return true;
}

try {
validateUser({ name: "А", email: "invalid", age: 200 });
} catch (error) {
if (error instanceof ValidationError) {
console.log(`${error.name}: ${error.message}`);
console.log(`Поле: ${error.field}`);
console.log(`Время: ${error.timestamp}`);
}
}

Особенности базовых объектов

Массив – основные методы:

МетодОписаниеПример
push() / pop()Добавить/удалить элемент в конецarr.push(4) → [1,2,3,4]
shift() / unshift()Удалить/добавить в началоarr.unshift(0) → [0,1,2,3]
slice()Копирует часть массиваarr.slice(1,3) → [2,3]
splice()Удаляет/заменяет элементыarr.splice(1,1) → [1,3]
map()Преобразует массив[1,2].map(x => x*2) → [2,4]
filter()Фильтрует элементы[1,2,3].filter(x => x>1) → [2,3]
reduce()Сворачивает массив в одно значение[1,2].reduce((a,b) => a+b) → 3
find()Находит первый подходящий элемент[1,2,3].find(x => x>1) → 2
sort()Сортирует массив[3,1].sort() → [1,3]
forEach(fn)Выполняет функцию для каждого элемента (ничего не возвращает)arr.forEach(el => console.log(el))
includes(value)Проверяет, есть ли элемент в массиве[1,2].includes(2) → true
indexOf(value)Возвращает индекс элемента или -1, если не найден[1,2,3].indexOf(2) → 1
some(fn)Проверяет, существует ли хотя бы один подходящий элемент[1,2].some(x => x > 1) → true
every(fn)Проверяет, все ли элементы соответствуют условию[2,3].every(x => x > 1) → true
join(separator)Преобразует массив в строку с указанным разделителем[1,2,3].join('-') → "1-2-3"
reverse()Разворачивает массив (мутирует оригинал)[1,2,3].reverse() → [3,2,1]
flat(n)Расплющивает вложенные массивы на n уровней[1, [2, [3]]].flat(2) → [1,2,3]
isArray()(статический)Проверяет, является ли значение массивомArray.isArray([1]) → true

Числа – основные методы и свойства:

Метод/ СвойствоОписаниеПример
Number.parseInt()Преобразует строку в целое числоparseInt("42") → 42
Number.parseFloat()Преобразует в дробное числоparseFloat("3.14") → 3.14
toFixed(n)Округляет до n знаков после запятой3.1415.toFixed(2) → "3.14"
toString()Преобразует число в строку42.toString() → "42"
Math.random()Случайное число от 0 до 1Math.random() → 0.123
Math.round()Округление до ближайшего целогоMath.round(3.6) → 4
Math.floor(x)Округляет вниз до целогоMath.floor(3.9) → 3
Math.ceil(x)Округляет вверх до целогоMath.ceil(3.1) → 4
Math.trunc(x)Убирает дробную часть, не округляяMath.trunc(3.9) → 3
Number.isNaN(value)Проверяет, является ли значение NaNNumber.isNaN(NaN) → true
Number.isFinite(value)Проверяет, является ли значение конечным числомNumber.isFinite(Infinity) → false
Number.isInteger(value)Проверяет, является ли число целымNumber.isInteger(42) → true
Math.pow(x, y)Возводит x в степень yMath.pow(2, 3) → 8
Math.sqrt(x)Квадратный корень из числаMath.sqrt(16) → 4
Math.min(...values)Находит минимальное числоMath.min(1, 2, 3) → 1
Math.max(...values)Находит максимальное числоMath.max(1, 2, 3) → 3
Number.MIN_VALUEМинимально возможное положительное числоNumber.MIN_VALUE → 5e-324
Number.MAX_VALUEМаксимально возможное числоNumber.MAX_VALUE → 1.7976...e+308

Строки – основные методы:

МетодОписаниеПример
lengthДлина строки"hello".length → 5
toUpperCase()Преобразует в верхний регистр"Hi".toUpperCase() → "HI"
toLowerCase()Преобразует в нижний регистр"Hi".toLowerCase() → "hi"
includes()Проверяет наличие подстроки"hello".includes("ell") → true
split()Разделяет строку в массив"a,b,c".split(",") → ["a","b","c"]
trim()Удаляет пробелы с обоих концов" hi ".trim() → "hi"
replace()Заменяет подстроку"hi".replace("i", "ello") → "hello"
indexOf(substring)Возвращает индекс первого вхождения подстроки, или -1, если не найдено"hello".indexOf("e") → 1
lastIndexOf(substring)Возвращает индекс последнего вхождения подстроки"abacaba".lastIndexOf("a") → 6
slice(start, end?)Возвращает часть строки между начальным и конечным индексами (не включая конец)"hello".slice(1,4) → "ell"
substring(start, end?)То же, что и slice, но не поддерживает отрицательные значения"hello".substring(1,4) → "ell"
charAt(index)Возвращает символ по указанному индексу"hello".charAt(0) → "h"
startsWith(prefix)Проверяет, начинается ли строка с указанной подстроки"hello".startsWith("he") → true
endsWith(suffix)Проверяет, заканчивается ли строка указанной подстрокой"hello".endsWith("lo") → true
repeat(count)Повторяет строку заданное число раз"ha".repeat(3) → "hahaha"

Глобальные функции – функции, которые доступны в глобальной области видимости:

ФункцияОписаниеПример
isNaN()Проверяет, является ли значение NaNisNaN("text") → true
parseInt()Аналог Number.parseInt()parseInt("42px") → 42
parseFloat()Аналог Number.parseFloat()parseFloat("3.14.15") → 3.14
eval()Выполняет строку как код (опасно!)eval("2+2") → 4
encodeURI()Кодирует URLencodeURI("https://сайт.рф") → "https://%D1%81%D0%B0%D0%B9%D1%82.%D1%80%D1%84"

Работа с датами (Date)

Date – объект, который используется для работы с временем.

const now = new Date(); // Текущая дата

console.log(now.getFullYear()); // Год (2023)
console.log(now.getMonth()); // Месяц (0-11)
console.log(now.getDate()); // День месяца (1-31)
console.log(now.getHours()); // Часы (0-23)
console.log(now.toLocaleString()); // "12.01.2023, 14:30:00"

DOM

DOM (Document Object Model)

DOM – программное представление HTML-документа в виде дерева объектов. Объект Document представляет собой весь HTML-документ и является корневым узлом дерева DOM. Он предоставляет методы и свойства для взаимодействия с содержимым страницы: поиска элементов, создания новых узлов, изменения содержимого, управления стилями и многим другим.

Основные сущности DOM:

СущностьОписание
DocumentКорень DOM (весь документ)
ElementHTML-элемент
AttrАтрибут элемента
TextТекстовый узел
CommentКомментарий
DocumentFragmentЛегковесный «контейнер» для DOM
NodeБазовый класс для всех узлов
NodeListКоллекция узлов
NamedNodeMapКоллекция атрибутов элемента

Свойства Document:

СвойствоОписаниеПример
document.documentElement Ссылка на корневой элемент <html>document.documentElement.tagName → "HTML"
document.headСсылка на элемент <head>document.head.appendChild(myScript)
document.bodyСсылка на элемент <body>document.body.innerHTML = "<h1>Привет</h1>"
document.titleПолучает или устанавливает заголовок страницы (<title>)document.title = "Новая страница"
document.URLВозвращает полный URL текущего документаconsole.log(document.URL)
document.locationОбъект Location, содержащий информацию о текущем адресеdocument.location.href
document.linksКоллекция всех гиперссылок (<a>) на страницеdocument.links[0].href
document.imagesКоллекция всех изображений (<img>) на страницеdocument.images.length
document.formsКоллекция всех форм на страницеdocument.forms.loginForm

Методы поиска элементов:

МетодПримерВозвращает
getElementById()document.getElementById('app')Один Element
getElementsByClassName()document.getElementsByClassName('item')HTMLCollection
getElementsByTagName()document.getElementsByTagName('div')HTMLCollection
querySelector()document.querySelector('.btn')Первый подходящий Element
querySelectorAll()document.querySelectorAll('p')NodeList

Методы создания элементов:

  • createElement() – создаёт элемент;
  • createTextNode() –создаёт текстовый узел;
  • createComment() – создаёт комментарий.

Свойства Element:

СвойствоЗначение
element.idзначение атрибута id
element.classNameстрока классов (class="...")
element.classListобъект для работы с классами (методы: add(), remove(), toggle())
element.innerHTMLHTML-содержимое
element.textContentтекст (без HTML-тегов)
element.styleдоступ к CSS-стилям

Методы:

МетодПримерДействие
getAttribute()div.getAttribute('data-id')Получить атрибут
setAttribute()div.setAttribute('data-test', '123')Установить атрибут
removeAttribute()div.removeAttribute('hidden')Удалить атрибут
append() / prepend()div.append(newElement)Добавить элемент
remove()div.remove()Удалить элемент
closest()div.closest('.parent')Найти ближайший родительский элемент

Прочие объекты

Браузерные объекты

Браузерные объекты

Браузерные объекты – объекты, предоставляемые для работы с окружением:

ОбъектОписание
WindowГлобальный объект (вкладка браузера)
NavigatorИнформация о браузере
ScreenДанные об экране
HistoryУправление историей
LocationURL страницы

Свойства Window:

СвойстваОписаниеПример
window.innerWidthШирина области просмотра (px)window.innerWidth → 1200
window.innerHeightВысота области просмотра (px)window.innerHeight → 800
window.outerWidthШирина всего окна браузера (px)window.outerWidth → 1400
window.outerHeightВысота всего окна браузера (px)window.outerHeight → 900
window.locationОбъект Location (URL страницы)window.location.href
window.documentОбъект Document (DOM)window.document.title
window.localStorageЛокальное хранилище данныхwindow.localStorage.setItem('key', 'value')

Методы:

МетодОписаниеПример
window.alert()Показывает alert-окноwindow.alert("Привет!")
window.open()Открывает новое окно/вкладкуwindow.open("https://google.com")
window.scrollTo()Прокручивает страницуwindow.scrollTo(0, 100)
window.setTimeout()Выполняет код с задержкойsetTimeout(() => {}, 1000)
window.fetch()Отправляет HTTP-запросfetch("https://api.example.com")

★ Графика: Canvas

Объект CanvasRenderingContext2D позволяет рисовать на <canvas>:

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 100); // Рисуем красный квадрат
```JavaScript

Работа с файлами и системой

В браузере и Node.js есть объекты для работы с файлами, сетью и процессами.

В браузере:

File API: Чтение файлов через `<input type= "file">`:
```JavaScript
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
console.log(file.name); // Имя файла
});

Основные методы/свойства File API:

  • files – список выбранных файлов;
  • FileReader – чтение содержимого файла;
  • Blob – бинарные данные файла.

В Node.js:

  • File System (fs): чтение/запись файлов – readFile, writeFile, promises;
  • HTTP/NET: создание серверов – createServer, request;
  • Buffer/Stream: работа с бинарными данными – Buffer.from(), stream.pipe();
  • Process: процессы – argv, cwd, exit.

И да, метода JSON.statham() нет. Вроде бы.


Структурированные данные

ArrayBuffer и SharedArrayBuffer

ArrayBuffer представляет собой буфер данных фиксированной длины, используемый для хранения бинарных данных:

// Создание буфера размером 16 байт
const buffer = new ArrayBuffer(16);

console.log(buffer.byteLength); // 16
console.log(buffer); // ArrayBuffer { byteLength: 16 }

Для работы с данными внутри буфера используются типизированные массивы:

const buffer = new ArrayBuffer(16);

// Создание представлений на буфер
const uint8View = new Uint8Array(buffer);
const uint16View = new Uint16Array(buffer);
const float32View = new Float32Array(buffer);

// Запись данных через одно представление
uint8View[0] = 255;
uint8View[1] = 128;
uint8View[2] = 64;
uint8View[3] = 32;

// Чтение через другое представление
console.log(uint16View[0]); // 32895
console.log(float32View[0]); // 1.539989614439558e-36

SharedArrayBuffer позволяет разделять буфер памяти между несколькими потоками выполнения (например, с помощью веб-воркеров):

// Создание разделяемого буфера
const sharedBuffer = new SharedArrayBuffer(1024);

// Создание представления
const sharedArray = new Int32Array(sharedBuffer);

// Запись данных
sharedArray[0] = 42;
sharedArray[1] = 100;

DataView

DataView предоставляет низкоуровневый интерфейс для чтения и записи данных произвольного типа в ArrayBuffer:

const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);

// Запись данных разных типов
view.setInt8(0, 127); // 1 байт
view.setInt16(1, 32767); // 2 байта
view.setInt32(3, 2147483647); // 4 байта
view.setFloat32(7, 3.14); // 4 байта
view.setFloat64(11, 2.71828); // 8 байт

// Чтение данных
console.log(view.getInt8(0)); // 127
console.log(view.getInt16(1)); // 32767
console.log(view.getInt32(3)); // 2147483647
console.log(view.getFloat32(7)); // 3.14
console.log(view.getFloat64(11)); // 2.71828

Указание порядка байтов (endianness):

const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);

// Запись с указанием порядка байтов
view.setInt32(0, 0x12345678, true); // little-endian
console.log(view.getInt32(0, true)); // 305419896

view.setInt32(0, 0x12345678, false); // big-endian
console.log(view.getInt32(0, false)); // 305419896

Atomics

Atomics предоставляет атомарные операции для работы с разделяемой памятью, обеспечивая потокобезопасность:

const sharedBuffer = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sharedBuffer);

// Атомарная запись
Atomics.store(sharedArray, 0, 100);
console.log(Atomics.load(sharedArray, 0)); // 100

// Атомарное сложение
Atomics.add(sharedArray, 0, 50);
console.log(Atomics.load(sharedArray, 0)); // 150

// Атомарное вычитание
Atomics.sub(sharedArray, 0, 30);
console.log(Atomics.load(sharedArray, 0)); // 120

// Атомарное сравнение и замена
Atomics.compareExchange(sharedArray, 0, 120, 200);
console.log(Atomics.load(sharedArray, 0)); // 200

// Атомарный инкремент
Atomics.add(sharedArray, 1, 1);
console.log(Atomics.load(sharedArray, 1)); // 1

Синхронизация потоков с помощью wait и notify:

// В основном потоке
Atomics.store(sharedArray, 0, 0);

// В воркере
Atomics.wait(sharedArray, 0, 0); // Ожидает, пока значение не изменится
console.log("Продолжение выполнения");

// В основном потоке после изменения
Atomics.store(sharedArray, 0, 1);
Atomics.notify(sharedArray, 0); // Пробуждает ожидающий воркер

JSON

Объект JSON предоставляет методы для преобразования данных в формат JSON и обратно:

// Преобразование объекта в JSON-строку
const user = {
name: "Алексей",
age: 30,
city: "Москва",
hobbies: ["чтение", "программирование"]
};

const jsonString = JSON.stringify(user);
console.log(jsonString);
// '{"name":"Алексей","age":30,"city":"Москва","hobbies":["чтение","программирование"]}'

// Преобразование строки в объект
const parsedUser = JSON.parse(jsonString);
console.log(parsedUser.name); // "Алексей"
console.log(parsedUser.hobbies[0]); // "чтение"

Форматирование при сериализации:

const data = {
title: "Продукт",
price: 1999,
inStock: true,
tags: ["новинка", "акция"]
};

// Без форматирования
console.log(JSON.stringify(data));
// {"title":"Продукт","price":1999,"inStock":true,"tags":["новинка","акция"]}

// С отступами (2 пробела)
console.log(JSON.stringify(data, null, 2));
/*
{
"title": "Продукт",
"price": 1999,
"inStock": true,
"tags": [
"новинка",
"акция"
]
}
*/

// С табуляцией
console.log(JSON.stringify(data, null, "\t"));

Использование функции-замены (replacer):

const product = {
name: "Ноутбук",
price: 50000,
costPrice: 30000, // Себестоимость (не показываем)
discount: 0.1,
inStock: true
};

// Исключение полей из сериализации
const jsonString = JSON.stringify(product, ["name", "price", "discount", "inStock"]);
console.log(jsonString);
// '{"name":"Ноутбук","price":50000,"discount":0.1,"inStock":true}'

// Преобразование значений
const formatted = JSON.stringify(product, (key, value) => {
if (key === "price") {
return value + " ₽";
}
if (key === "discount") {
return (value * 100) + "%";
}
return value;
});

console.log(formatted);
// '{"name":"Ноутбук","price":"50000 ₽","costPrice":30000,"discount":"10%","inStock":true}'

Управление памятью

WeakRef

WeakRef создаёт слабую ссылку на объект, позволяя сборщику мусора удалить объект, если на него нет других сильных ссылок:

class CacheItem {
constructor(value) {
this.value = value;
this.timestamp = Date.now();
}
}

const cache = new Map();

function addToCache(key, value) {
const item = new CacheItem(value);
cache.set(key, new WeakRef(item));
return item;
}

function getFromCache(key) {
const ref = cache.get(key);
if (ref) {
return ref.deref();
}
return undefined;
}

const obj = addToCache("user1", { name: "Иван" });
console.log(getFromCache("user1")); // CacheItem { value: { name: "Иван" }, timestamp: ... }

// После удаления сильной ссылки объект может быть собран
obj = null;
setTimeout(() => {
console.log(getFromCache("user1")); // undefined (возможно)
}, 100);

FinalizationRegistry

FinalizationRegistry регистрирует колбэк, который вызывается после удаления объекта сборщиком мусора:

const registry = new FinalizationRegistry(heldValue => {
console.log(`Объект освобождён: ${heldValue}`);
});

class Resource {
constructor(name) {
this.name = name;
this.buffer = new ArrayBuffer(1024 * 1024); // 1 МБ
registry.register(this, name, this);
}

cleanup() {
registry.unregister(this);
console.log(`Ресурс ${this.name} очищен вручную`);
}
}

// Создание ресурса
const resource1 = new Resource("файл1");
const resource2 = new Resource("файл2");

// Удаление ссылок
resource1 = null;
resource2.cleanup();
resource2 = null;

// Через некоторое время в консоли:
// "Объект освобождён: файл1"

Практическое применение для управления ресурсами:

class ImageLoader {
constructor() {
this.cache = new Map();
this.registry = new FinalizationRegistry(key => {
console.log(`Изображение ${key} удалено из кэша`);
this.cache.delete(key);
});
}

load(url) {
if (this.cache.has(url)) {
return this.cache.get(url);
}

const image = new Image();
image.src = url;

this.cache.set(url, image);
this.registry.register(image, url);

return image;
}

clear() {
this.cache.clear();
console.log("Кэш изображений очищен");
}
}

const loader = new ImageLoader();
const img1 = loader.load("image1.jpg");
const img2 = loader.load("image2.jpg");

// После удаления ссылок изображения будут удалены из кэша

Абстракции управления

Iterator и AsyncIterator

Итераторы предоставляют стандартный способ перебора элементов коллекции:

// Создание кастомного итератора
const range = {
from: 1,
to: 5,

[Symbol.iterator]() {
let current = this.from;
const last = this.to;

return {
next() {
if (current <= last) {
return { value: current++, done: false };
} else {
return { done: true };
}
}
};
}
};

// Использование итератора
for (let num of range) {
console.log(num); // 1, 2, 3, 4, 5
}

// Распространение в массив
const arr = [...range];
console.log(arr); // [1, 2, 3, 4, 5]

Асинхронные итераторы для работы с асинхронными данными:

const asyncRange = {
from: 1,
to: 3,

async *[Symbol.asyncIterator]() {
for (let i = this.from; i <= this.to; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
}
};

// Использование асинхронного итератора
(async () => {
for await (let num of asyncRange) {
console.log(num); // 1 (через 1с), 2 (через 2с), 3 (через 3с)
}
})();

Promise

Promise представляет результат асинхронной операции:

// Создание промиса
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve("Операция завершена успешно");
} else {
reject(new Error("Произошла ошибка"));
}
}, 1000);
});

// Обработка результата
promise
.then(result => console.log(result))
.catch(error => console.error(error.message));

// Цепочка промисов
fetch("https://api.example.com/data")
.then(response => response.json())
.then(data => processData(data))
.then(result => saveResult(result))
.catch(error => console.error("Ошибка:", error));

Статические методы Promise:

// Promise.all - ожидание всех промисов
Promise.all([
fetch("/api/users"),
fetch("/api/posts"),
fetch("/api/comments")
])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(([users, posts, comments]) => {
console.log("Данные загружены:", { users, posts, comments });
});

// Promise.race - возвращает первый завершённый промис
Promise.race([
fetch("/api/data"),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Таймаут")), 5000)
)
])
.then(data => console.log("Данные получены"))
.catch(error => console.error("Ошибка:", error));

// Promise.allSettled - ожидает все промисы независимо от результата
Promise.allSettled([
Promise.resolve(1),
Promise.reject(new Error("Ошибка")),
Promise.resolve(3)
])
.then(results => {
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`Промис ${index} выполнен:`, result.value);
} else {
console.log(`Промис ${index} отклонён:`, result.reason);
}
});
});

Generator и GeneratorFunction

Генераторы позволяют приостанавливать и возобновлять выполнение функции:

// Объявление генератора
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
return 4;
}

const gen = numberGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: 4, done: true }
console.log(gen.next()); // { value: undefined, done: true }

Генератор с параметрами:

function* counter() {
let count = 0;
while (true) {
const increment = yield count;
count += increment || 1;
}
}

const gen = counter();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next(5).value); // 6
console.log(gen.next(10).value); // 16

Делегирование генераторов:

function* gen1() {
yield 1;
yield 2;
}

function* gen2() {
yield 3;
yield* gen1(); // Делегирование
yield 4;
}

const gen = gen2();
console.log([...gen]); // [3, 1, 2, 4]

AsyncGenerator и AsyncGeneratorFunction

Асинхронные генераторы объединяют функциональность генераторов и промисов:

async function* asyncNumberGenerator() {
yield 1;
await new Promise(resolve => setTimeout(resolve, 1000));
yield 2;
await new Promise(resolve => setTimeout(resolve, 1000));
yield 3;
}

(async () => {
for await (let num of asyncNumberGenerator()) {
console.log(num); // 1, затем 2 (через 1с), затем 3 (через 2с)
}
})();

Практическое применение для потоковой загрузки данных:

async function* fetchPages(url, pages) {
for (let page = 1; page <= pages; page++) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
yield data;
}
}

(async () => {
for await (const pageData of fetchPages("/api/items", 3)) {
console.log("Загружена страница:", pageData);
}
})();

AsyncFunction

AsyncFunction — это конструктор для создания асинхронных функций динамически:

const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;

const asyncSum = new AsyncFunction('a', 'b', 'return a + b');

asyncSum(5, 3).then(result => console.log(result)); // 8

DisposableStack и AsyncDisposableStack

Эти объекты упрощают управление ресурсами, требующими очистки:

// DisposableStack для синхронных ресурсов
class FileHandle {
constructor(filename) {
this.filename = filename;
this.open = true;
console.log(`Файл ${filename} открыт`);
}

read() {
return `Содержимое файла ${this.filename}`;
}

[Symbol.dispose]() {
if (this.open) {
console.log(`Файл ${this.filename} закрыт`);
this.open = false;
}
}
}

function processFile() {
using file = new FileHandle("data.txt");
console.log(file.read());
// Файл автоматически закроется при выходе из блока
}

processFile();

Асинхронная версия:

class DatabaseConnection {
constructor(connectionString) {
this.connectionString = connectionString;
this.connected = true;
console.log(`Подключение к базе данных установлено`);
}

async query(sql) {
return `Результат запроса: ${sql}`;
}

async [Symbol.asyncDispose]() {
if (this.connected) {
console.log(`Подключение к базе данных закрыто`);
this.connected = false;
}
}
}

async function processData() {
await using db = new DatabaseConnection("connection-string");
const result = await db.query("SELECT * FROM users");
console.log(result);
// Подключение автоматически закроется при выходе из блока
}

await processData();

Рефлексия

Reflect

Reflect предоставляет методы для выполнения операций над объектами в функциональном стиле:

const obj = { name: "Тест", value: 42 };

// Получение свойства
console.log(Reflect.get(obj, "name")); // "Тест"

// Установка свойства
Reflect.set(obj, "value", 100);
console.log(obj.value); // 100

// Проверка наличия свойства
console.log(Reflect.has(obj, "name")); // true
console.log(Reflect.has(obj, "missing")); // false

// Удаление свойства
Reflect.deleteProperty(obj, "value");
console.log("value" in obj); // false

// Применение функции
function sum(a, b) {
return a + b;
}

console.log(Reflect.apply(sum, null, [5, 3])); // 8

// Создание экземпляра
class Person {
constructor(name) {
this.name = name;
}
}

const person = Reflect.construct(Person, ["Алексей"]);
console.log(person.name); // "Алексей"

Proxy

Proxy позволяет перехватывать и переопределять операции над объектами:

const target = {
message: "Привет",
count: 0
};

const handler = {
get(target, prop, receiver) {
console.log(`Чтение свойства: ${prop}`);
return Reflect.get(target, prop, receiver);
},

set(target, prop, value, receiver) {
console.log(`Запись свойства ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver);
},

has(target, prop) {
console.log(`Проверка наличия: ${prop}`);
return Reflect.has(target, prop);
},

deleteProperty(target, prop) {
console.log(`Удаление свойства: ${prop}`);
return Reflect.deleteProperty(target, prop);
}
};

const proxy = new Proxy(target, handler);

proxy.message; // "Чтение свойства: message"
proxy.count = 5; // "Запись свойства count = 5"
"name" in proxy; // "Проверка наличия: name"
delete proxy.message; // "Удаление свойства: message"

Валидация данных через прокси:

const validator = {
set(obj, prop, value) {
if (prop === "age") {
if (typeof value !== "number" || value < 0 || value > 150) {
throw new RangeError("Возраст должен быть от 0 до 150");
}
}
if (prop === "email") {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
throw new TypeError("Некорректный формат email");
}
}
return Reflect.set(obj, prop, value);
}
};

const user = new Proxy({}, validator);
user.age = 25; // OK
user.email = "test@example.com"; // OK
// user.age = 200; // RangeError
// user.email = "invalid"; // TypeError

Создание неизменяемого объекта:

function makeImmutable(obj) {
return new Proxy(obj, {
set(target, prop, value) {
throw new Error(`Нельзя изменить свойство ${prop}`);
},
deleteProperty(target, prop) {
throw new Error(`Нельзя удалить свойство ${prop}`);
}
});
}

const config = makeImmutable({
apiUrl: "https://api.example.com",
timeout: 5000,
debug: false
});

// config.apiUrl = "new-url"; // Error
// delete config.timeout; // Error

Интернационализация

Intl

Объект Intl предоставляет интернационализированные функции форматирования:

// Текущая локаль
console.log(Intl.getCanonicalLocales("en-US")); // ["en-US"]
console.log(Intl.getCanonicalLocales("RU")); // ["ru"]

Intl.Collator

Collator обеспечивает сортировку строк с учётом локали:

const names = ["Иван", "Анна", "Петр", "Елена"];

// Сортировка без учёта локали
console.log(names.sort());
// ["Анна", "Елена", "Иван", "Петр"]

// Сортировка с учётом русской локали
const collator = new Intl.Collator("ru");
console.log(names.sort((a, b) => collator.compare(a, b)));
// ["Анна", "Елена", "Иван", "Петр"]

// Сортировка с игнорированием регистра
const caseInsensitive = new Intl.Collator("ru", { sensitivity: "base" });
console.log(caseInsensitive.compare("Тест", "тест")); // 0 (равны)

Intl.DateTimeFormat

Форматирование дат и времени:

const date = new Date(2024, 0, 15, 14, 30, 45);

// Различные форматы для русской локали
console.log(new Intl.DateTimeFormat("ru-RU").format(date));
// "15.01.2024"

console.log(new Intl.DateTimeFormat("ru-RU", { dateStyle: "full" }).format(date));
// "понедельник, 15 января 2024 г."

console.log(new Intl.DateTimeFormat("ru-RU", {
dateStyle: "long",
timeStyle: "short"
}).format(date));
// "15 января 2024 г., 14:30"

console.log(new Intl.DateTimeFormat("ru-RU", {
year: "numeric",
month: "long",
day: "numeric",
weekday: "long",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
timeZoneName: "short"
}).format(date));
// "понедельник, 15 января 2024 г., 14:30:45, MSK"

Форматирование для разных локалей:

const date = new Date(2024, 0, 15);

console.log(new Intl.DateTimeFormat("en-US").format(date)); // "1/15/2024"
console.log(new Intl.DateTimeFormat("de-DE").format(date)); // "15.1.2024"
console.log(new Intl.DateTimeFormat("ja-JP").format(date)); // "2024/1/15"
console.log(new Intl.DateTimeFormat("zh-CN").format(date)); // "2024/1/15"

Intl.NumberFormat

Форматирование чисел:

const number = 1234567.89;

// Форматирование для русской локали
console.log(new Intl.NumberFormat("ru-RU").format(number));
// "1 234 567,89"

// Валюта
console.log(new Intl.NumberFormat("ru-RU", {
style: "currency",
currency: "RUB"
}).format(number));
// "1 234 567,89 ₽"

console.log(new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"
}).format(number));
// "$1,234,567.89"

// Проценты
console.log(new Intl.NumberFormat("ru-RU", {
style: "percent"
}).format(0.42));
// "42 %"

// Научная нотация
console.log(new Intl.NumberFormat("ru-RU", {
notation: "scientific"
}).format(number));
// "1,23456789E6"

// Компактный формат
console.log(new Intl.NumberFormat("ru-RU", {
notation: "compact"
}).format(1234567));
// "1,2 млн"

Intl.RelativeTimeFormat

Форматирование относительного времени:

const formatter = new Intl.RelativeTimeFormat("ru", { numeric: "auto" });

console.log(formatter.format(-1, "day")); // "вчера"
console.log(formatter.format(0, "day")); // "сегодня"
console.log(formatter.format(1, "day")); // "завтра"
console.log(formatter.format(-2, "day")); // "позавчера"
console.log(formatter.format(2, "day")); // "послезавтра"

console.log(formatter.format(-1, "hour")); // "час назад"
console.log(formatter.format(1, "hour")); // "через час"
console.log(formatter.format(3, "hour")); // "через 3 часа"

console.log(formatter.format(-1, "month")); // "в прошлом месяце"
console.log(formatter.format(1, "month")); // "в следующем месяце"

Intl.ListFormat

Форматирование списков:

const formatter = new Intl.ListFormat("ru", { 
style: "long",
type: "conjunction"
});

console.log(formatter.format(["яблоки"])); // "яблоки"
console.log(formatter.format(["яблоки", "груши"])); // "яблоки и груши"
console.log(formatter.format(["яблоки", "груши", "апельсины"]));
// "яблоки, груши и апельсины"

// Другие типы
const disjunction = new Intl.ListFormat("ru", { type: "disjunction" });
console.log(disjunction.format(["да", "нет"])); // "да или нет"

const unit = new Intl.ListFormat("ru", { type: "unit" });
console.log(unit.format(["метр", "килограмм"])); // "метр, килограмм"

Intl.PluralRules

Определение формы множественного числа:

const rules = new Intl.PluralRules("ru");

console.log(rules.select(1)); // "one"
console.log(rules.select(2)); // "few"
console.log(rules.select(5)); // "many"
console.log(rules.select(11)); // "many"
console.log(rules.select(21)); // "one"
console.log(rules.select(22)); // "few"

// Практическое применение
function getMessage(count) {
const forms = {
one: "сообщение",
few: "сообщения",
many: "сообщений"
};
return `${count} ${forms[rules.select(count)]}`;
}

console.log(getMessage(1)); // "1 сообщение"
console.log(getMessage(2)); // "2 сообщения"
console.log(getMessage(5)); // "5 сообщений"
console.log(getMessage(21)); // "21 сообщение"

Intl.DisplayNames

Отображение названий локалей, регионов и языков:

const displayNames = new Intl.DisplayNames("ru", { type: "language" });

console.log(displayNames.of("en")); // "английский"
console.log(displayNames.of("de")); // "немецкий"
console.log(displayNames.of("fr")); // "французский"
console.log(displayNames.of("zh")); // "китайский"

// Названия регионов
const regionNames = new Intl.DisplayNames("ru", { type: "region" });
console.log(regionNames.of("US")); // "США"
console.log(regionNames.of("DE")); // "Германия"
console.log(regionNames.of("CN")); // "Китай"
console.log(regionNames.of("RU")); // "Россия"

// Названия скриптов
const scriptNames = new Intl.DisplayNames("ru", { type: "script" });
console.log(scriptNames.of("Latn")); // "латиница"
console.log(scriptNames.of("Cyrl")); // "кириллица"

Intl.Segmenter

Сегментация текста на графемы, слова или предложения:

// Сегментация на слова
const wordSegmenter = new Intl.Segmenter("ru", { granularity: "word" });
const text = "Привет, мир! Как дела?";
const segments = wordSegmenter.segment(text);

for (const segment of segments) {
console.log(segment.segment);
}
// "Привет"
// "мир"
// "Как"
// "дела"

// Сегментация на предложения
const sentenceSegmenter = new Intl.Segmenter("ru", { granularity: "sentence" });
const sentences = sentenceSegmenter.segment("Первое предложение. Второе предложение! Третье?");

for (const segment of sentences) {
console.log(segment.segment);
}
// "Первое предложение."
// "Второе предложение!"
// "Третье?"

Intl.Locale

Работа с локалями:

const locale = new Intl.Locale("ru-RU");

console.log(locale.language); // "ru"
console.log(locale.script); // undefined
console.log(locale.region); // "RU"
console.log(locale.baseName); // "ru-RU"

// Методы
console.log(locale.maximize()); // "ru-Cyrl-RU" (добавлен скрипт)
console.log(locale.minimize()); // "ru" (убран регион)

// Создание с опциями
const localeWithOptions = new Intl.Locale("ru", {
region: "RU",
calendar: "gregory",
hourCycle: "h23"
});

console.log(localeWithOptions.toString());
// "ru-RU-u-ca-gregory-hc-h23"

Практические примеры

Создание кэша с автоматической очисткой

class SmartCache {
constructor(maxSize = 100) {
this.cache = new Map();
this.maxSize = maxSize;
this.registry = new FinalizationRegistry(key => {
console.log(`Ключ ${key} удалён из кэша`);
});
}

set(key, value) {
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}

this.cache.set(key, new WeakRef(value));
this.registry.register(value, key);
}

get(key) {
const ref = this.cache.get(key);
return ref ? ref.deref() : undefined;
}

has(key) {
return this.cache.has(key);
}

delete(key) {
return this.cache.delete(key);
}

clear() {
this.cache.clear();
console.log("Кэш очищен");
}

size() {
return this.cache.size;
}
}

const cache = new SmartCache(3);
cache.set("user1", { name: "Иван" });
cache.set("user2", { name: "Мария" });
cache.set("user3", { name: "Алексей" });

console.log(cache.get("user1")); // { name: "Иван" }
console.log(cache.size()); // 3

Интернационализированный форматтер дат

class DateFormatter {
constructor(locale = "ru-RU", options = {}) {
this.locale = locale;
this.options = options;
this.formatter = new Intl.DateTimeFormat(locale, options);
}

format(date) {
return this.formatter.format(date);
}

formatRange(startDate, endDate) {
return `${this.format(startDate)} - ${this.format(endDate)}`;
}

formatRelative(date) {
const diff = date - Date.now();
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);

const formatter = new Intl.RelativeTimeFormat(this.locale, {
numeric: "auto"
});

if (Math.abs(days) >= 1) {
return formatter.format(days, "day");
} else if (Math.abs(hours) >= 1) {
return formatter.format(hours, "hour");
} else if (Math.abs(minutes) >= 1) {
return formatter.format(minutes, "minute");
} else {
return formatter.format(seconds, "second");
}
}
}

const formatter = new DateFormatter("ru-RU", {
dateStyle: "long",
timeStyle: "short"
});

const now = new Date();
const tomorrow = new Date(now);
tomorrow.setDate(tomorrow.getDate() + 1);

console.log(formatter.format(now)); // "15 января 2024 г., 14:30"
console.log(formatter.formatRange(now, tomorrow)); // "15 января 2024 г., 14:30 - 16 января 2024 г., 14:30"
console.log(formatter.formatRelative(tomorrow)); // "завтра"